package org.cainiao.basic.concurrent.lock.mcs;

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public class MCSTest {

    /**
     * MCS锁节点
     */
    public static class MCSNodeV2 {
        /**
         * 后继节点
         */
        volatile MCSNodeV2 next;

        /**
         * 默认状态为等待锁
         */
        volatile boolean blocked = true;
    }

    /**
     * 线程到节点的映射
     */
    private ThreadLocal<MCSNodeV2> currentThreadNode = new ThreadLocal<>();

    /**
     * 指向最后一个申请锁的MCSNode
     */
    volatile MCSNodeV2 queue;

    /**
     * 原子更新器
     */
    private static final AtomicReferenceFieldUpdater<MCSTest, MCSNodeV2> UPDATER =
        AtomicReferenceFieldUpdater.newUpdater(MCSTest.class, MCSNodeV2.class, "queue");

    /**
     * MCS获取锁操作
     */
    public void lock() {
        MCSNodeV2 cNode = currentThreadNode.get();

        if (cNode == null) {
            // 初始化节点对象
            cNode = new MCSNodeV2();
            currentThreadNode.set(cNode);
        }

        // 将当前申请锁的线程置为queue并返回旧值
        // getAndSet方法会用CAS + 自旋重试，直到设置成功为止
        // 返回的是成功更新值之前获取的CAS期望值
        MCSNodeV2 predecessor = UPDATER.getAndSet(this, cNode); // step 1

        if (predecessor == null) {
            // 自己是头节点
            // 1）表示自己抢到锁
            // 2）不会有前驱通知自己解锁
            cNode.blocked = false;
        } else {
            // 形成链表结构(单向)
            predecessor.next = cNode; // step 2

            // 当前线程处于等待状态时自旋(MCSNode的blocked初始化为true)
            // 等待前驱节点主动通知，即将blocked设置为false，表示当前线程可以获取到锁
            while (cNode.blocked) {
            }
        }
    }

    /**
     * MCS释放锁操作
     */
    public void unlock() {
        // 获取当前线程对应的节点
        MCSNodeV2 cNode = currentThreadNode.get();

        if (cNode == null || cNode.blocked) {
            // 当前线程对应存在节点
            // 并且
            // 锁拥有者进行释放锁才有意义 - 当blocked未true时，表示此线程处于等待状态中，并没有获取到锁，因此没有权利释放锁
            return;
        }

        // 这一步是让queue不要指向cNode
        // 因为queue上放的节点会被下一次加入队列的节点当做前驱
        // 即下一个节点会等着当前节点唤醒它(unlock)
        // 既然当前节点此时正在执行unlock，那么今后不会再次去unlock别人了，所以要从queue上下架
        // cNode.next不为null，那么cNode肯定不在queue上，因此不用置换queue
        // cNode.next==null，那么cNode在queue上，需要将其从queue上取下，即让queue指向null
        // 如果置换的中途，有别的节点插入进来，即compareAndSet失败
        // 那么需要while等待下一个节点的lock操作让cNode.next指向新节点
        // compareAndSet不会自旋，CAS失败直接返回false，对比getAndSet
        if (cNode.next == null && !UPDATER.compareAndSet(this, cNode, null)) {
            while (cNode.next == null) {
            }
        }

        if (cNode.next != null) {
            // 通知后继节点可以获取锁
            cNode.next.blocked = false;
            // 当前节点的任务已完成，不会再使用，从链表中断开，表示可以被GC回收
            cNode.next = null;
        }

        // 清空当前线程对应的节点信息
        currentThreadNode.remove();
    }

    public static void main(String[] args) {
        final MCSTest lock = new MCSTest();

        for (int i = 1; i <= 10; i++) {
            new Thread(generateTask(lock, String.valueOf(i))).start();
        }
    }

    private static Runnable generateTask(final MCSTest lock, final String taskId) {
        return () -> {
            lock.lock();
            try {
                try {
                    Thread.sleep(3000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(String.format("Thread %s Completed", taskId));
            } finally {
                lock.unlock();
            }
        };
    }

}
